/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */

package map;

import java.util.Random;

import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;

import sq.ImageLoader;
import sq.RPG;
import sq.FontLoader;

/** The weather generator.
 *
 */
public class Weather {
	
	// maximum number of rain drops
	public static int MAX_DROPS = 200;
	// maximum time a rain drop is shown
	public static int TIMER_MAX = 200;
	// the length of rain in hours
	public static float RAIN_LENGTH = 1.5f;
	// the gap of two raining sessions in hours
	public static float RAIN_GAP = 4;
	// one game hour equals, 10 seconds
	public static int ONE_HOUR = 10000;
	// the number of hours per day
	public static int HOURS_PER_DAY = 24;
	// the interval between lighting strikes
	public static int LIGHTNING_INTERVAL = 2000;
	
	public static int LIGHTNING_MAX_IMAGES = 3;
	
	private static int NOT_LIGHTNING = -1, LIGHTNING_SESSION = 4, LIGHTNING_MAX = 10;
	
	private static Random rnd;		// random number generator
	
	private int dayHours;
	private float lightings;
	
	private boolean raining;
	private float rainAngle;
	private int rainingLength;
	private int rainTimer = TIMER_MAX;
	private float rainGap;
	private float cameraX, cameraY;
	private int lightningIndex, lightningGap;
	
	private World world;
	
	private float rainDrops[][];	// holds the drawing coordinates as [x1,y1,x2,y2][DROP_NUM]
	private Lightning lightnings[];
	
	/** Creates a new Weather object
	 * @param world The associated world
	 * @param StartingHour The starting hour of day
	 * @throws SlickException
	 */
	public Weather(World world, int StartingHour)
	throws SlickException
	{
		
		rnd = new Random();
		rainAngle = (float) (-Math.PI/4);
		rainingLength = 0;
		
		this.setWorld(world);
		
		this.loadLightnings();
		
		this.setRaining(false);
		this.setLightings(1f);
		this.setHourOfDay(StartingHour);
		this.resetRainGap();
		this.resetRainDrops();
		
	}
    
    /** Update the player's state for a frame.
     * @param world A reference to the world.
     * @param delta Time passed since last frame (milliseconds).
     */
    public void update(World world, int delta, float camX, float camY)
    throws SlickException
    {
    	
    	dayHours += delta;
    	dayHours %= (24*ONE_HOUR);	// keep the time for 24 hours
    	
    	float MIDDAY = HOURS_PER_DAY/2, hour = this.getHourOfDay();
    	if (hour < MIDDAY) {
    		this.setLightings((float)(hour/MIDDAY));
    	} else {
    		float lighting = (hour-MIDDAY)/MIDDAY;
    		this.setLightings(1f - (float)lighting);
    	}
    	
    	this.updateRain(delta, camX, camY);
    	this.updateLightning(delta, camX, camY);
    		
    }
    
    /** Update the rain state for a frame.
     * @param delta Time passed since last frame (milliseconds).
     */
    private void updateRain(int delta, float camX, float camY) {
    	
    	this.rainingLength += delta;
    	
    	if (this.isRaining()) {
    		
    		if (this.getRainDuration() < RAIN_LENGTH) {
	    		rainTimer += delta;
	    		int TIMER_MAX = 200;
	    		this.cameraX = camX;
	    		this.cameraY = camY;
	    		if (rainTimer >= TIMER_MAX) { this.resetRainDrops(); }
    		} else {
    			this.setRaining(false);
    			this.resetRainGap();
    		}
	    	
    	} else {
    		
    		if (this.getRainDuration() >= this.getRainGap()) {
    			this.setRaining(true);
    			this.resetRainGap();
    		}
    		
    	}
    	
    }
    
    /** Update the rain state for a frame.
     * @param delta Time passed since last frame (milliseconds).
     */
    private void updateLightning(int delta, float camX, float camY) {
    	
    	if (this.isRaining()) {
    		
    		this.setLightningGap(this.getLightningGap()-delta);
    		
    		if (this.isLightning()) {
    	    		
    	    		lightnings[this.getLightningIndex()].update(this.getWorld(), delta);
    	    		if (!lightnings[this.getLightningIndex()].isActive()) {
    	    			this.setLightningIndex(NOT_LIGHTNING);
    	    			this.setLightningGap(LIGHTNING_INTERVAL);
    	    		}
    	    		
    	    } else {
	    		
	    		if (this.getLightningGap() <= 0) {
	    			
	    			this.setLightningGap(LIGHTNING_INTERVAL);
	    			
			    	for (int i = 0 ; i < LIGHTNING_MAX ; i++) {
			    		Lightning l = lightnings[i];
			    		if (l.isAllowedStrike()) {
			    			float height = RPG.getScreenWidth()/4;
			        		float x = Math.abs(getRandomInt())%RPG.getScreenWidth();   		
			        		float y = (Math.abs(getRandomInt())%(RPG.getScreenHeight()-height))+height;
			        		l.strike(this.getWorld().getCameraX()+x, this.getWorld().getCameraY()+y);
			    			this.setLightningIndex(i);
			    			break;
			    		}
			    	}
			    	
	    		}
		    	
	    	}
    	
    	}
    	
    }
     
    
    /** Render the entire screen, so it reflects the current game state.
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the x-direction (pixels).
     */
    public void render(Graphics g, float cameraOffsetX, float cameraOffsetY)
    {
    	
    	this.renderLightning(g, cameraOffsetX, cameraOffsetX);
    	this.renderRain(g, cameraOffsetX, cameraOffsetY);
    	g.setColor(new Color(0f,0f,0f,(1f-this.getLightings())/2));
    	
    	g.fillRect(0f, 0f, RPG.getScreenWidth(), RPG.getScreenHeight());
    	
    	
    }
    
    /** Render the rain drops on the entire screen.
     * @param g The Slick graphics object, used for drawing.
     */
    private void renderRain(Graphics g, float camX, float camY) {
    	
    	this.renderLightning(g, camX, camY);
    	g.setLineWidth(2);
    	if (this.isRaining()) {
    		g.setColor(FontLoader.getRainColor());
    		int LEN = rainDrops[1].length;
    		float cX = (float) (camX - this.cameraX);
    		float cY = (float) (camY - this.cameraY);
	    	for (int i = 0 ; i < LEN ; i++) {
		    	g.drawLine(rainDrops[0][i]-cX, rainDrops[1][i]-cY, rainDrops[2][i]-cX, rainDrops[3][i]-cY);
	    	}
    	}
    	
    }
    
    /** Render the rain drops on the entire screen.
     * @param g The Slick graphics object, used for drawing.
     */
    private void renderLightning(Graphics g, float camX, float camY) {
    	
    	if (this.isRaining()) {
    		
    		for (Lightning l : this.lightnings) {
    			l.render(g, camX, camY);
    		}
    		
    	}
    	
    }
    
    /** Render the rain drops on the entire screen.
     * @param g The Slick graphics object, used for drawing.
     * @param x The x coordinate as to where the images should be drawn (in pixels).
     * @param y The y coordinate as to where the images should be drawn (in pixels).
     */
    public void renderHour(Graphics g, float x, float y) {
    	
    	int top = (int) Math.floor(this.getHourOfDay());
    	int bottom = ((top==0)?ImageLoader.getWeatherHours().length:top)-1;
    	float alpha = (float)(this.getHourOfDay() - Math.floor(this.getHourOfDay()));
    	ImageLoader.getWeatherHour(bottom).setAlpha(1-alpha);
    	ImageLoader.getWeatherHour(bottom).draw(x, y);
    	ImageLoader.getWeatherHour(top).setAlpha(alpha);
    	ImageLoader.getWeatherHour(top).draw(x, y);
    	
    	if (this.isRaining()) { ImageLoader.getWeatherRain().draw(x, y); }
    	
    }
    
    public float getHourOfDay() {
    	return this.dayHours/ONE_HOUR;
    }
    
    public void setHourOfDay(float hour) {
    	this.dayHours = (int) (hour*ONE_HOUR);
    }
    
    private float getRainDuration() {
    	return this.rainingLength/ONE_HOUR;
    }
    
    private void resetRainingDuration() {
    	this.rainingLength = 0;
    }
    
    private void resetRainGap() {
    	this.setRainGap((getRandomInt()%(RAIN_GAP/2))+RAIN_GAP);
    }
    
    private void resetRainDrops() {
    	
    	int LEN = (MAX_DROPS/2)+(Math.abs(getRandomInt())%(MAX_DROPS/2));
		rainDrops = new float[4][LEN];
		rainTimer = 0;
		
		int DROPLEN = 10;

    	float DX = (float) (DROPLEN*Math.cos(rainAngle));
    	float DY = (float) (DROPLEN*Math.sin(rainAngle));
    	
    	for (int i = 0 ; i < LEN ; i++) {
    		rainDrops[0][i] = Math.abs(getRandomInt())%RPG.getScreenWidth();   		
    		rainDrops[1][i] = Math.abs(getRandomInt())%RPG.getScreenHeight();   		
    		rainDrops[2][i] = rainDrops[0][i]+DX;   		
    		rainDrops[3][i] = rainDrops[1][i]+DY;
    	}
    	
    }
    
    public boolean isNowDayTime() {
    	float hour = this.getHourOfDay();
    	return (hour>8 && hour<18);
    }
    
    
	/* Getters and Setters */

    private boolean isRaining() {
		return this.raining;
	}
 
    private void setRaining(boolean raining) {
		this.resetRainingDuration();
		this.raining = raining;
		if (this.isRaining()) { this.resetLightning(); }
	}

    private float getLightings() {
		return this.lightings;
	}

    private void setLightings(float value) {
		this.lightings = (value<0f?0f:(value>1f?1f:value));
	}

    private float getRainGap() {
		return this.rainGap;
	}

    private void setRainGap(float rainGap) {
		this.rainGap = rainGap;
	}
    
    private void loadLightnings() 
    throws SlickException 
    {
    	
    	lightnings = new Lightning[LIGHTNING_MAX];
    	for (int i = 0 ; i < LIGHTNING_MAX ; i++) {
    		int index = Math.abs(getRandomInt())%LIGHTNING_MAX_IMAGES;
    		this.lightnings[i] = new Lightning(ImageLoader.getWeatherLightning(index));
    	}
    	
    }
    
    private static int getRandomInt() {
    	return rnd.nextInt();
    }
    
    private void resetLightning() {
    	
    	this.setLightningIndex(NOT_LIGHTNING);
    	this.setLightningGap(Math.abs(getRandomInt())%LIGHTNING_INTERVAL);
    	
    	for (Lightning l : this.lightnings) {
    		l.reset();
    	}
    	
    	int count = 0;
    	while (count < LIGHTNING_SESSION) {
    		if (this.allowLightning()) {
    			count++;
    		}
    	}
    	
    }
    
    private boolean allowLightning() {
    	
    	int i = (int) Math.abs(getRandomInt())%LIGHTNING_MAX;
    	if ((i >= 0) && (i < LIGHTNING_MAX) && !this.lightnings[i].isAllowedStrike()) {
    		this.lightnings[i].allowStrike();
    		return true;
    	}
    	return false;
    	
    }

	private void setLightningIndex(int index) {
		this.lightningIndex = index;
	}
	
	private int getLightningIndex() {
		return this.lightningIndex;
	}
	
	private boolean isLightning() {
		return ((this.getLightningIndex() >= 0) && (this.getLightningIndex() < LIGHTNING_MAX));
	}

	private int getLightningGap() {
		return this.lightningGap;
	}

	private void setLightningGap(int lightningGap) {
		this.lightningGap = lightningGap;
	}

	private World getWorld() {
		return world;
	}

	private void setWorld(World world) {
		this.world = world;
	}

}
